Skip to content

Feat/support ref implementation#92

Open
jhateley-godaddy wants to merge 4 commits into
mainfrom
feat/support-ref-implementation
Open

Feat/support ref implementation#92
jhateley-godaddy wants to merge 4 commits into
mainfrom
feat/support-ref-implementation

Conversation

@jhateley-godaddy

@jhateley-godaddy jhateley-godaddy commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Related issue

Fixes #91

Summary

Consequences of the v2 spec's model changes:

  • AgentRevocationRequest.ReasonEnum → replaced by top-level RevocationReason model (RegistrationClient.revokeAgent)
  • AgentCapabilityRequest no longer generated (v1-only endpoint dropped from v2 spec) — added as a hand-written local DTO in ans-sdk-discovery to keep the deprecated POST /v1/agents/resolution path working
  • AgentEndpoint.ProtocolEnum no longer generated replaced by top-level Protocol model
  • README updated to point at the new spec source instead of the old GoDaddy developer portal links

New files

  • ApiVersion.java (ans-sdk-core/config) — enum V1 (/v1/agents) | V2 (/v2/ans/agents). V2 is the default for new clients; both lanes stay live.
  • AgentPaths.java (ans-sdk-registration) — central path builder:
    • agentsCollectionPath(...)
    • registerPath(...) — v2 = collection POST, v1 = /register verb
    • agentPath(version, id, ...segments)
    • Keeps all lane branching out of inline string concatenation.

AnsConfiguration

  • Add apiVersion field + builder setter (defaults V2, null-checked).
  • Relax build validation: was environment required → now environment or baseUrl required.
  • Tests: default-V2, override-V1, null-throws, updated validation message.

RegistrationClient

  • New apiVersion(...) builder method → forwards to config.
  • Pass configuration into RegistrationService.

RegistrationService (version-aware)

  • Constructor now takes AnsConfiguration, reads apiVersion.
  • All paths routed through AgentPaths (register / get / verify-acme / verify-dns / revoke).
  • v1: strip v2-only discoveryProfiles field from the wire body; follow HATEOAS self link for details.
  • v2: use pending.getAgentId() (UUID) directly to fetch details; throw if missing.

CertificateService (version-aware)

  • Reads apiVersion; identity/server cert paths via AgentPaths.

AnsApiClient

  • Add serializeToJsonWithoutField(obj, field) — strips a top-level JSON field (e.g. discoveryProfiles) for v1 without mutating the caller's object.

ResolutionService

  • Javadoc only: document that resolution is intentionally pinned to v1 (/v1/agents/resolution, /v1/agents/{id}) — no v2 equivalent by design.

Tests

  • CertificateServiceTest — add v1-lane routing tests; flip existing stubs /v1/agents/.../v2/ans/agents/... (v2 now default).
  • RegistrationClientTest — same v2-default path flips (/v1/agents/register/v2/ans/agents, etc.).

Testing

Tests updated, e2e tests ran against local RA (v2) and separate (v1) implementation

AI assistance

None

Checklist

  • The PR title follows Conventional Commits — release notes are generated from it
  • Tests cover the change
  • The linked issue above uses a closing keyword
  • Every commit is signed off (git commit -s) certifying the DCO

Signed-off-by: James Hateley <jhateley@godaddy.com>
Signed-off-by: James Hateley <jhateley@godaddy.com>
@jhateley-godaddy
jhateley-godaddy marked this pull request as ready for review July 23, 2026 03:27
Signed-off-by: James Hateley <jhateley@godaddy.com>
Comment on lines +123 to +150
if ("self".equals(link.getRel()) && link.getHref() != null) {
if ("self".equals(link.getRel())) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we remove the null check on link.getHref() here?

@jhateley-godaddy jhateley-godaddy Jul 24, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reverted - test cases added.

Comment on lines -185 to +189
// Handle both enum name (HTTP_API) and value (HTTP-API)
return protocolStr.equals(endpointProtocol.name())
|| protocolStr.equals(endpointProtocol.getValue());
// Normalize hyphens to underscores so "HTTP-API" form matches HTTP_API
String normalized = protocolStr.replace('-', '_');
return normalized.equals(endpointProtocol.name())
|| normalized.equals(endpointProtocol.getValue());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After normalizing protocolStr to use underscores, the second comparison against getValue() (which returns e.g. "HTTP_API") is redundant with the first comparison against name() — they return the same string for the new top-level Protocol enum. The second clause is effectively dead.

If the intent is to keep both the legacy hyphenated alias and the canonical form working, the original protocolStr should still be compared against getValue() before normalization:

  return protocolStr.equals(endpointProtocol.name())
      || protocolStr.equals(endpointProtocol.getValue())
      || protocolStr.replace('-', '_').equals(endpointProtocol.name());

Alternatively, we can remove the 2nd OR branch.

@jhateley-godaddy jhateley-godaddy Jul 24, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Protocol enum still contains an entry where the name doesn't match the value:

  A2_A("A2A")

so I think we still need this. There is an existing test in AgentConnectionTest:

@Test
    void supportsProtocolWithSupportedProtocolShouldReturnTrue() {
        // Given
        AgentConnection connection = new AgentConnection(agentDetails, ansHttpClient);

        // When/Then - using enum name format
        assertThat(connection.supportsProtocol("HTTP_API")).isTrue();
        assertThat(connection.supportsProtocol("A2A")).isTrue();
    }

Comment on lines +183 to +193
String serializeToJsonWithoutField(Object object, String fieldName) {
try {
JsonNode tree = objectMapper.readTree(objectMapper.writeValueAsString(object));
if (tree instanceof ObjectNode objectNode) {
objectNode.remove(fieldName);
}
return objectMapper.writeValueAsString(tree);
} catch (IOException e) {
throw new AnsServerException("Failed to serialize request: " + e.getMessage(), 0, e, null);
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor one - According to Claude, line 185 serializes to string then immediately re-parses. objectMapper.valueToTree(object) avoids the intermediate string allocation:

JsonNode tree = objectMapper.valueToTree(object);
if (tree instanceof ObjectNode objectNode) {
    objectNode.remove(fieldName);
}

return objectMapper.writeValueAsString(tree);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

Signed-off-by: James Hateley <jhateley@godaddy.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: sync with ans api-spec

2 participants